use std::{
    any::{Any, TypeId},
    collections::HashMap,
    ops::{Deref, DerefMut},
};

use bevy::{ecs::system::EntityCommands, prelude::*, utils::all_tuples};
pub struct UiChildBuilder<'a1, 'a, 'b, 'c, 'd> {
    pub builder: &'a mut ChildBuilder<'b, 'c, 'd>,
    pub game_resource: UnsafeGameResource<'a1>,
}

impl<'a, 'a1, 'b, 'c, 'd> UiChildBuilder<'a, 'a1, 'b, 'c, 'd> {
    pub fn spawn(&mut self, bundle: impl Bundle) -> UiCommands<'a, 'b, 'c, '_> {
        let commands: EntityCommands<'b, 'c, '_> = self.builder.spawn(bundle);
        UiCommands {
            commands,
            game_resource: self.game_resource,
        }
    }

    pub fn insert_bundle<T: 'static>(&mut self, res: T) -> &mut Self {
        self.game_resource.insert_bundle(res);
        self
    }

    pub fn take_bundle<T: 'static>(&mut self) -> T {
        self.game_resource.take_bundle::<T>()
    }
}

pub struct UiCommands<'a, 'b, 'c, 'd> {
    pub commands: EntityCommands<'b, 'c, 'd>,
    pub game_resource: UnsafeGameResource<'a>,
}

impl<'a, 'b, 'c, 'd> UiCommands<'a, 'b, 'c, 'd> {
    pub fn with_children(&mut self, f: impl FnOnce(&mut UiChildBuilder)) {
        self.commands.with_children(|children| {
            f(&mut UiChildBuilder {
                builder: children,
                game_resource: self.game_resource,
            });
        });
    }
}

pub struct GameCommands<'a, 'b, 'c> {
    commands: &'a mut Commands<'b, 'c>,
    pub game_resource: GameResource<'a>,
}

impl <'a, 'b, 'c>GameCommands<'a, 'b, 'c> {
    pub fn new(commands: &'a mut Commands<'b, 'c>) -> Self {
        Self {
            commands,
            game_resource: GameResource::new(),
        }
    }

    pub fn insert_bundle<T: 'static>(&mut self, res: T) -> &mut Self {
        self.game_resource.insert_bundle(res);
        self
    }

    pub fn insert_mut<T: Resource>(&mut self, res: &'a mut T) -> &mut Self {
        self.game_resource.insert_mut(res);
        self
    }

    pub fn insert<T: Resource>(&mut self, res: &'a T) -> &mut Self {
        self.game_resource.insert(res);
        self
    }

    pub fn take_bundle<T: 'static>(&mut self) -> T {
        self.game_resource.take_bundle::<T>()
    }
    
    pub fn spawn(&mut self, bundle: impl Bundle) -> UiCommands<'a, 'b, 'c, '_> {
        let commands: EntityCommands<'b, 'c, '_> = self.commands.spawn(bundle);
        UiCommands {
            commands,
            game_resource: self.game_resource.as_unsafe(),
        }
    }
}

pub struct GameResource<'ui> {
    res_ref: HashMap<TypeId, Box<&'ui dyn Any>>,
    res_mut: HashMap<TypeId, Box<&'ui mut dyn Any>>,
    res: HashMap<TypeId, Box<dyn Any>>,
}

unsafe impl Send for GameResource<'_> {}

#[derive(Clone, Copy)]
pub struct UnsafeGameResource<'ui>(*mut GameResource<'ui>);
unsafe impl<'ui> Send for UnsafeGameResource<'ui> {}

impl<'a> UnsafeGameResource<'a> {
    pub fn new(game_resource: &mut GameResource<'a>) -> Self {
        Self(game_resource as *mut GameResource)
    }

    pub fn get<T: Resource>(&self) -> Option<&'a T> {
        unsafe { (*self.0).get::<T>() }
    }

    pub fn get_mut<T: Resource>(&self) -> Option<&'a mut T> {
        unsafe { (*self.0).get_mut::<T>() }
    }

    pub fn take_bundle<T: 'static>(&self) -> T {
        unsafe { (*self.0).take_bundle::<T>() }
    }

    pub fn get_bundle_mut<T: 'static>(&self) -> Option<&'a mut T> {
        unsafe { (*self.0).get_bundle_mut::<T>() }
    }

    pub fn insert<T: Resource>(&self, res: &'a T) -> &Self {
        unsafe {
            (*self.0).insert(res);
        }
        self
    }

    pub fn insert_mut<T: Resource>(&self, res: &'a mut T) -> &Self {
        unsafe {
            (*self.0).insert_mut(res);
        }
        self
    }

    pub fn insert_bundle<T: 'static>(&self, res: T) -> &Self {
        unsafe {
            (*self.0).insert_bundle(res);
        }
        self
    }
}

trait ClassParam<'a> {
    fn get_param(game_resource: UnsafeGameResource<'a>) -> Self;
}

impl<'a, T: Resource> ClassParam<'a> for CRes<'a, T> {
    fn get_param(game_resource: UnsafeGameResource<'a>) -> Self {
        Self {
            val: game_resource.get::<T>().unwrap(),
        }
    }
}

impl<'a, T: Resource> ClassParam<'a> for CResMut<'a, T> {
    fn get_param(game_resource: UnsafeGameResource<'a>) -> Self {
        Self {
            val: game_resource.get_mut::<T>().unwrap(),
        }
    }
}

impl<'a, B: 'static> ClassParam<'a> for Qb<'a, B> {
    fn get_param(game_resource: UnsafeGameResource<'a>) -> Self {
        Self {
            val: game_resource.get_bundle_mut::<B>().unwrap(),
        }
    }
}

pub struct CRes<'a, T: Resource> {
    val: &'a T,
}

impl<T: Resource> Deref for CRes<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.val
    }
}

impl<T: Resource> AsRef<T> for CRes<'_, T> {
    fn as_ref(&self) -> &T {
        self.val
    }
}
#[derive(Debug)]
pub struct CResMut<'a, T> {
    val: &'a mut T,
}

impl<T> Deref for CResMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.val
    }
}

impl<T> DerefMut for CResMut<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.val
    }
}

impl<T> AsRef<T> for CResMut<'_, T> {
    fn as_ref(&self) -> &T {
        self.val
    }
}

impl<T> AsMut<T> for CResMut<'_, T> {
    fn as_mut(&mut self) -> &mut T {
        self.val
    }
}

pub struct Qb<'a, B: 'static> {
    val: &'a mut B,
}

impl<'b, B: 'static> Deref for Qb<'b, B> {
    type Target = B;

    fn deref(&self) -> &Self::Target {
        self.val
    }
}

impl<'b, B: 'static> DerefMut for Qb<'b, B> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.val
    }
}

impl<'a, B: 'static> AsRef<B> for Qb<'a, B> {
    fn as_ref(&self) -> &B {
        self.val
    }
}

impl<'a, B: 'static> AsMut<B> for Qb<'a, B> {
    fn as_mut(&mut self) -> &mut B {
        self.val
    }
}

impl<'a> GameResource<'a> {
    pub fn new() -> Self {
        Self {
            res_ref: HashMap::default(),
            res_mut: HashMap::default(),
            res: HashMap::default(),
        }
    }

    pub fn as_unsafe(&mut self) -> UnsafeGameResource<'a> {
        UnsafeGameResource::new(self)
    }

    pub fn insert_bundle<T: 'static>(&mut self, res: T) -> &mut Self {
        self.res.insert(TypeId::of::<T>(), Box::new(res));
        self
    }

    pub fn insert<T: Resource>(&mut self, res: &'a T) -> &mut Self {
        self.res_ref.insert(TypeId::of::<T>(), Box::new(res));
        self
    }

    pub fn insert_mut<T: Resource>(&mut self, res: &'a mut T) -> &mut Self {
        self.res_mut.insert(TypeId::of::<T>(), Box::new(res));
        self
    }

    pub fn get<T: Resource>(&'a self) -> Option<&'a T> {
        self.res_ref
            .get(&TypeId::of::<T>())
            .map(|res| res.downcast_ref::<T>().unwrap())
    }

    pub fn get_mut<T: Resource>(&'a mut self) -> Option<&'a mut T> {
        self.res_mut
            .get_mut(&TypeId::of::<T>())
            .map(|res| res.downcast_mut::<T>().unwrap())
    }

    pub fn take_bundle<T: 'static>(&mut self) -> T {
        self.res
            .remove(&TypeId::of::<T>())
            .map(|res| *res.downcast::<T>().unwrap())
            .unwrap()
    }

    pub fn get_bundle_mut<T: 'static>(&mut self) -> Option<&mut T> {
        self.res
            .get_mut(&TypeId::of::<T>())
            .map(|res| res.downcast_mut::<T>().unwrap())
    }
}

pub trait Class<'a, P> {
    fn apply(self, game_resource: UnsafeGameResource<'a>);
}

macro_rules! impl_class_tuple {
    ($($P: ident),*) => {
        impl<'a,F, $($P),*> Class<'a, ($($P,)*)> for F
        where
            F: FnOnce($($P), *),
            $($P: ClassParam<'a>,)*
        {
            #[allow(unused_variables)]
            fn apply(self, game_resource: UnsafeGameResource<'a>) {
                self($($P::get_param(game_resource),)*);
            }
        }
    }
}

all_tuples!(impl_class_tuple, 0, 10, P);

macro_rules! impl_class_more_tuple {
    ($(($P: ident, $p: ident)),*) => {
        #[allow(non_snake_case)]
        impl<'a, $($P, $p),*> Class<'a, ($($P,)*)> for ($($p,)*)
        where
            $($p: Class<'a, $P>,)*
        {
            #[allow(unused_variables)]
            fn apply(self, game_resource: UnsafeGameResource<'a>) {
                let ($($p,)*) = self;
                $($p.apply(game_resource);)*
            }
        }
    };
}

all_tuples!(impl_class_more_tuple, 0, 10, P, S);
